package com.zyy.mvcframework.servlet;

import com.zyy.mvcframework.annotation.*;
import com.zyy.mvcframework.pojo.Handler;
import com.zyy.mvcframework.pojo.ResultHandler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author zyy
 * @date 2020/12/8 4:42 下午
 * @description
 */
public class MyDispatcherServlet extends HttpServlet {
    private Properties properties = new Properties();
    //扫描缓存到的类的全限定类名
    private List<String> classNames = new ArrayList<>();

    //ioc容器 用来存放实例化的bean的Ioc容器
    private Map<String,Object> ioc = new HashMap<>();

    /**
     * 安全标识 字段
     */
    private String SECURITY_SIGN;
    //handlerMapping
//    private Map<String,Object> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);
        //2.扫描相关的类 扫描注解
        doScan(properties.getProperty("scanPackage"));
        SECURITY_SIGN = properties.getProperty("securitySign");
        //3.初始化Bean对象 （实现IOC容器 基于注解）
        doInstance();
        //4.实现依赖注入
        doAutowired();
        //5.构造一个HandlerMapping处理器映射器 将处理好的Url和methed建立映射关系
        initHandlerMapping();
        System.out.println("My mvc 初始化完成....");

        //6.等待请求进入，处理请求
    }

    /**
     * 构造一个HandlerMapping处理器映射器 将处理的
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()){return;}

        //遍历ioc中的所有对象，查看类信息是否有MyController注解，是否有@MyRequestMapping注解，获取baseUrl
        //遍历该类中的方法集合，查看是否有MyRequestMapping注解，获取methodUrl
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(MyController.class)) {
                continue;
            }
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                //等同于 /demo
                baseUrl = aClass.getAnnotation(MyRequestMapping.class).value();
            }
            //判断类上有无MySecurity注解
            String[] baseSecurityNames = new String[0];
            if (aClass.isAnnotationPresent(MySecurity.class)) {
                baseSecurityNames = aClass.getAnnotation(MySecurity.class).value();
            }

            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String methodUrl = annotation.value();
                    String url = baseUrl + methodUrl;
                    //判断方法上有无MySecurity注解
                    String[] methodSecurityNames = new String[0];
                    if(method.isAnnotationPresent(MySecurity.class)){
                        methodSecurityNames = method.getAnnotation(MySecurity.class).value();
                    }
                    Set<String> securityNames = new HashSet<>(Arrays.asList(baseSecurityNames));
                    securityNames.addAll(Arrays.asList(methodSecurityNames));
                    //建立url和method之间的映射关系
                    //把method的所有信息封装成一个url
                    Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url),securityNames);

                    //计算方法中的参数位置
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class){
                            //如果是request和response对象 那么参数名称简写为HttpServletRequest和HttpServletResponse
                            handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),i);
                        }else {
                            //<name,2>
                            handler.getParamIndexMapping().put(parameter.getName(),i);
                        }
                    }
                    handlerMapping.add(handler);
//                    handlerMapping.put(url,entry.getValue());
                }
            }
        }
    }

    /**
     * 实现依赖注入
     */
    private void doAutowired() {
        if(ioc.isEmpty()){
            return;
        }

        try {
            //遍历ioc中的所有对象，查看对象中的字段，是否有@MyAutowired注解，如果有需要维护依赖注入关系
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                //获取Bean中的字段信息
                Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    //查看该字段中有无MyAutowired注解
                    if (declaredField.isAnnotationPresent(MyAutowired.class)){
                        //获取MyAutowired的value值作为beanName
                        String beanName = declaredField.getAnnotation(MyAutowired.class).value();
                        if("".equals(beanName.trim())){
                            //如果没有设置具体的value,则需要根据当前属性所属类型进行注入（接口注入）
                            beanName = lowFirst(declaredField.getType().getSimpleName());
                        }
                        //开启赋值
                        declaredField.setAccessible(true);
                        declaredField.set(entry.getValue(),ioc.get(beanName));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * ioc容器
     * 基于ClassName缓存的类的全限定名，以及反射技术，完成对象创建和管理
     */
    private void doInstance() {
        if(classNames.size() == 0) {
            return;
        }

        try {
            for (int i = 0; i < classNames.size(); i++) {
                //com.zyy.demo.controller.DemoController
                String className = classNames.get(i);
                //反射获取类
                Class<?> aClass = Class.forName(className);
                // 区分controller，区分service'
                //如果是MyController注解则直接获取类名的首字母小写作为BeanName
                if (aClass.isAnnotationPresent(MyController.class)) {
                    String beanName = lowFirst(aClass.getSimpleName());
                    ioc.put(beanName,aClass.newInstance());
                }else if(aClass.isAnnotationPresent(MyService.class)){
                    //若是MyService注解则需获取注解中的value值 若值为空则以类名的首字母小写作为BeanName
                    MyService annotation = aClass.getAnnotation(MyService.class);
                    String beanName = annotation.value();
                    if("".equals(beanName.trim())){
                        beanName = lowFirst(aClass.getSimpleName());
                    }
                    ioc.put(beanName,aClass.newInstance());

                    //判断是否实现了接口 若实现了接口 则将接口名称的首字母小写作为BeanName放入IOC容器中
                    //便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        String interfaceBeanName = lowFirst(anInterface.getSimpleName());
                        ioc.put(interfaceBeanName,aClass.newInstance());
                    }
                }else{
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将类名的首字母小写
     * @param className
     * @return
     */
    private String lowFirst(String className){
        char[] chars = className.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if(chars[0] >= 'A' && chars[0]<='Z'){
                chars[0] += 32;
            }
        }
        return String.valueOf(chars);
    }

    /**
     * 扫描相关的类 扫描注解
     * 包的话无法对它的子包进行获取并递归 但是文件夹可以
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        //将包名转化为文件的位置
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            //子Package 递归
            if (file.isDirectory()) {
                //com.zyy.demo.controller
                doScan(scanPackage + "." + file.getName());
            }else if(file.getName().endsWith(".class")){
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }

        }
    }

    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理请求
        //根据Uri获取到能处理当前请求的handler(从handlerMapping中)
        ResultHandler resultHandler = getHandler(req);
        switch (resultHandler.getCode()){
            case 404:
            case 403:
                resp.getWriter().write(resultHandler.getMsg());
                return;
        }
        Handler handler = resultHandler.getHandler();
        Method method = handler.getMethod();
        //根据方法获取参数的长度
        int parameterCount = method.getParameterCount();
        //根据上述长度创建一个新的Object数组
        Object [] paramValues = new Object[parameterCount];

        //向参数数组中塞值 同时需要保证参数数组中的顺序需要跟方法中的顺序保持一致
        Map<String,String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            //name=1&name=2 name=[1,2]
            // 如同 1,2
            String value = StringUtils.join(param.getValue(), ",");
            //如果参数和方法中的参数匹配上了 ，填充数据
            if(handler.getParamIndexMapping().containsKey(param.getKey())){
                // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放入paramValues
                Integer index = handler.getParamIndexMapping().get(param.getKey());
                paramValues[index] = value;
            }
        }
        //如果参数为HttpServletRequest
        if(handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getSimpleName())){
            Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
            paramValues[requestIndex] = req;
        }

        if (handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getSimpleName())){
            Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
            paramValues[responseIndex] = resp;
        }

        try {
            method.invoke(handler.getObj(),paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从处理器映射器列表中获取Handler
     * @param req
     * @return
     */
    private ResultHandler getHandler(HttpServletRequest req) {
        ResultHandler resultHandler = new ResultHandler();
        resultHandler.setCode(404);
        resultHandler.setMsg("404 not found");
        if (handlerMapping.isEmpty()) {
            return resultHandler;
        }

        String url = req.getRequestURI();
        String securitySign = req.getParameter(SECURITY_SIGN);
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (matcher.matches()){
                //判断有无访问权限
                Set<String> securityNames = handler.getSecurityNames();
                //如果securityNames为空，则代表没有进行权限控制 不做处理
                // 否则进行校验
                if (!securityNames.isEmpty()){
                    //若req中没有这个子段或者handler权限中不包含这个username,则返回没有访问权限
                    if (securitySign == null || !securityNames.contains(securitySign.trim())) {
                        resultHandler.setCode(403);
                        resultHandler.setMsg("403 Unauthorized");
                        return resultHandler;
                    }
                }
                resultHandler.setCode(200);
                resultHandler.setMsg("success");
                resultHandler.setHandler(handler);
                return  resultHandler;
            }
        }
        return resultHandler;
    }

}
